﻿using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using SdmxMl.Common;
using SdmxMl.Helper;
using SdmxMl.Manager;
using SdmxMl.GenericMetadata;

namespace SdmxMl.Structure
{
    /// <summary>
    /// Metadata attributes are those concepts - whether taking a coded or uncoded value,
    /// or made up of child concepts, or both - which are reported against a full or partial
    /// target identifier. If there are nested metadata attributes, these concepts are
    /// subordinate to the parent metadata attribute - that is, for the purposes of
    /// presentation, the parent concept is made up of the child concepts. This hierarchy
    /// is strictly presentational, for the purposes of structuring reports. If the
    /// metadata attribute can have a coded or uncoded value, then the charateristics of
    /// the value are indicated with the TextFormat child element. If the value is coded,
    /// then the representationScheme and representationSchemeAgency attributes must hold
    /// values: the representationScheme attribute takes the ID of a representation scheme,
    /// and the representationSchemeAgency takes the ID of the agency which maintains that
    /// scheme. The conceptRef attribute holds the ID of the metadata attribute's concept.
    /// The conceptAgency attribute takes the agency ID of the concept referenced in
    /// conceptRef. The conceptSchemeRef attribute holds the ID value of the concept
    /// scheme from which the concept is taken, and the conceptSchemeAgency holds the ID
    /// of the agency that maintains the concept scheme referenced in the conceptSchemeRef
    /// attribute. The useageStatus attribute indicates whether provision of the metadata
    /// attribute is conditional or mandatory.
    /// </summary>
    public class MetadataAttributeType : IAnnotationList
    {

        #region Properties

        public MetaAttributeOccurences MetaOccurence { get; set; }
        public UsageStatusType UsageStatus
        {
            get { return MetaOccurence.UsageStatus; }
        }

        public ConceptReference ConceptInfo { get; set; }

        private ConceptSchemeReference _conceptSchemeInfo;
        public ConceptSchemeReference ConceptSchemeInfo
        {
            get { return _conceptSchemeInfo; }
        }

        private RepresentationReference _representationInfo;
        public RepresentationReference RepresentationInfo
        {
            get { return _representationInfo; }
        }

        public TextFormatType TextFormat { get; set; }

        public AnnotationList AnnotationList { get; set; }

        /// <summary></summary>
        private MetadataAttributeTypeList _metadataAttributeList;
        public MetadataAttributeTypeList MetadataAttributeList
        {
            get { return _metadataAttributeList; }
        }

        // SDMX 2.1 only
        public string Id { get; set; }

        #endregion //Properties

        #region Construction
        public MetadataAttributeType()
        {
            Init();
            MetaOccurence = new MetaAttributeOccurences(UsageStatusType.Conditional, false);
        }

        private void Init()
        {
            _representationInfo = new RepresentationReference();
            ConceptInfo = new ConceptReference();
            _conceptSchemeInfo = new ConceptSchemeReference();
            TextFormat = new TextFormatType();
            AnnotationList = new AnnotationList();
            _metadataAttributeList = new MetadataAttributeTypeList();
        }

        public MetadataAttributeType(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr) 
        {
            AnnotationList = new AnnotationList();
            XmlNodeList nodeList = null;

            if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
            {
                UsageStatusType usageStat;
                string str = Xmlhelper.GetAtt(nodeArtefact, "usageStatus");
                if (str.Length > 0)
                {
                    if (Enum.IsDefined(typeof(UsageStatusType), str))
                        usageStat = (UsageStatusType)Enum.Parse(typeof(UsageStatusType), str, true);
                    else
                        throw new ApplicationException("Unrecognised Usage status: " + str);
                }
                else
                    usageStat = UsageStatusType.Conditional;

                MetaOccurence = new MetaAttributeOccurences(usageStat, false);

                ConceptInfo = new ConceptReference(nodeArtefact, nsmgr);
                Id = ConceptInfo.Id;
                _conceptSchemeInfo = new ConceptSchemeReference(nodeArtefact, nsmgr);
                _representationInfo = new RepresentationReference(nodeArtefact, nsmgr);
                TextFormat = new TextFormatType(nodeArtefact, nsmgr);

                // Annotations ?
                nodeList = nodeArtefact.SelectNodes("./structure:Annotations/common:Annotation", nsmgr);
                foreach (XmlNode na in nodeList)
                {
                    Annotation a = new Annotation(na, nsmgr);
                    AnnotationList.Add(a);
                }
                MetaOccurence.RetrieveFormXmlByAnnotation(this.AnnotationList);

                TextFormatHelperForSdmx21Mimic tfmimic = new TextFormatHelperForSdmx21Mimic(TextFormat, this.AnnotationList);
                tfmimic.RetrieveFormXmlByAnnotation();

            }
            else // SDMX 2,1
            {
                _representationInfo = new RepresentationReference();
                ConceptInfo = new ConceptReference();
                _conceptSchemeInfo = new ConceptSchemeReference();
                TextFormat = new TextFormatType();

                string query = string.Empty;

                Id = Xmlhelper.GetAtt(nodeArtefact, "id");

                // Annotations ?
                nodeList = nodeArtefact.SelectNodes("./common:Annotations/common:Annotation", nsmgr);
                foreach (XmlNode na in nodeList)
                {
                    Annotation a = new Annotation(na, nsmgr);
                    AnnotationList.Add(a);
                }

                int minOccurs = 1;
                string str = Xmlhelper.GetAtt(nodeArtefact, "minOccurs");
                if (string.IsNullOrEmpty(str) == false)
                    minOccurs = Convert.ToInt32(str);

                int maxOccurs = 1;
                str = Xmlhelper.GetAtt(nodeArtefact, "maxOccurs");
                if (string.IsNullOrEmpty(str) == false)
                    maxOccurs = Convert.ToInt32(str);

                bool IsPresentational = Xmlhelper.GetAtt(nodeArtefact, "isPresentational").ToLower() == "true";

                MetaOccurence = new MetaAttributeOccurences(IsPresentational, minOccurs, maxOccurs);

                XmlNode ndConceptIdentity = nodeArtefact.SelectSingleNode("./structure:ConceptIdentity/Ref", nsmgr);
                if (ndConceptIdentity != null)
                {
                    ConceptInfo.Id = Xmlhelper.GetAtt(ndConceptIdentity, "id");
                    _conceptSchemeInfo.Id = Xmlhelper.GetAtt(ndConceptIdentity, "maintainableParentID");
                    _conceptSchemeInfo.AgencyId = Xmlhelper.GetAtt(ndConceptIdentity, "agencyID");
                    _conceptSchemeInfo.Version = Xmlhelper.GetAtt(ndConceptIdentity, "maintainableParentVersion");
                }
                else
                {
                    // Try via URN
                    query = "./structure:ConceptIdentity/URN";
                    XmlNode ndCoreRef = nodeArtefact.SelectSingleNode(query, nsmgr);
                    if (ndCoreRef != null)
                    {
                        string urn = UrnParser.UrnSdmx_21_to_20(ndCoreRef.InnerText);
                        UrnParser p = new UrnParser(urn);
                        _conceptSchemeInfo.AgencyId = p.AgencyId;
                        _conceptSchemeInfo.Id = p.ArtefactId;
                        _conceptSchemeInfo.Version = p.Version;
                        ConceptInfo.Id = p.SubTypeId;
                    }
                }

                // Has Local Representation ?

                // Has TextFormat
                query = "./structure:LocalRepresentation";
                XmlNode ndTF = nodeArtefact.SelectSingleNode(query, nsmgr);
                if (ndTF != null)
                    TextFormat = new TextFormatType(ndTF, nsmgr);

                // Has codelist
                XmlNode ndCL = nodeArtefact.SelectSingleNode("./structure:LocalRepresentation/structure:Enumeration/Ref", nsmgr);
                if (ndCL != null)
                {
                    _representationInfo.Id = Xmlhelper.GetAtt(ndCL, "id");
                    _representationInfo.AgencyId = Xmlhelper.GetAtt(ndCL, "agencyID");
                    _representationInfo.Version = Xmlhelper.GetAtt(ndCL, "version");
                }
                else
                {
                    // Try via URN
                    query = "./structure:LocalRepresentation/structure:Enumeration/URN";
                    XmlNode ndCoreRef = nodeArtefact.SelectSingleNode(query, nsmgr);
                    if (ndCoreRef != null)
                    {
                        string urn = UrnParser.UrnSdmx_21_to_20(ndCoreRef.InnerText);
                        UrnParser p = new UrnParser(urn);
                        _representationInfo.AgencyId = p.AgencyId;
                        _representationInfo.Id = p.ArtefactId;
                        _representationInfo.Version = p.Version;
                    }
                }

            }

            // Meta Attributes child 
            _metadataAttributeList = new MetadataAttributeTypeList();
            nodeList = nodeArtefact.SelectNodes("./structure:MetadataAttribute", nsmgr);
            foreach (XmlNode nm in nodeList)
            {
                MetadataAttributeType a = new MetadataAttributeType(nm, nsmgr);
                _metadataAttributeList.Add(a);
            }



        }
        #endregion //Construction

        #region XML serialize

        protected void WriteReferences(XmlTextWriter xw)
        {
            ConceptInfo.WriteXml(xw);
            ConceptSchemeInfo.WriteXml(xw);
            RepresentationInfo.WriteXml(xw);

        }

        public void WriteXml(XmlTextWriter xw, string prefix, string tag)
        {
            xw.WriteStartElement(prefix, tag, null);

            if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_20)
            {
                WriteReferences(xw);
                // Required
                xw.WriteAttributeString("usageStatus", MetaOccurence.UsageStatus.ToString());

                // Child MetadataAttribute list
                foreach (MetadataAttributeType meta in _metadataAttributeList)
                    meta.WriteXml(xw, prefix, "MetadataAttribute");

                // Text Format
                TextFormat.WriteXml(xw, prefix, "TextFormat");

                // Annotations & SDMX 2.1 mimic
                MetaOccurence.PersistToXmlByAnnotation(xw, prefix, this.AnnotationList, TextFormat);
            }
            else // SDMX 2.1
            {
                // Urn, Uri, Id
                xw.WriteAttributeString("id", Id);

                // min, max occurs, ispresentational
                if (MetaOccurence.MinUnspecified == false)
                    xw.WriteAttributeString("minOccurs", MetaOccurence.MinOccurs.ToString());
                if (MetaOccurence.MaxUnspecified == false)
                    xw.WriteAttributeString("maxOccurs", MetaOccurence.MaxOccurs.ToString());
                if (MetaOccurence.IsPresentational)
                    xw.WriteAttributeString("isPresentational", "true");

                // Annotations
                AnnotationList.WriteXml(xw, "common", null);

                // Concept identity
                xw.WriteStartElement(prefix, "ConceptIdentity", null);
                xw.WriteStartElement("Ref");
                xw.WriteAttributeString("id", ConceptInfo.Id);
                xw.WriteAttributeString("maintainableParentID", ConceptSchemeInfo.Id);
                xw.WriteAttributeString("agencyID", ConceptSchemeInfo.AgencyId);
                if (ConceptSchemeInfo.Version != "1.0")
                    xw.WriteAttributeString("maintainableParentVersion", ConceptSchemeInfo.Version);
                xw.WriteEndElement();
                xw.WriteEndElement();

                // Local Representation
                if (TextFormat.TextType != TextTypeType.Undefined || RepresentationInfo.IsFilled)
                {
                    xw.WriteStartElement(prefix, "LocalRepresentation", null);

                    // Text Format only if no codelist ref
                    if (TextFormat.TextType != TextTypeType.Undefined && !RepresentationInfo.IsFilled)
                        TextFormat.WriteXml(xw, prefix, "TextFormat");

                    if (RepresentationInfo.IsFilled)
                    {
                        xw.WriteStartElement(prefix, "Enumeration", null);
                        xw.WriteStartElement("Ref");
                        Xmlhelper.WriteAttributeIfExist(xw, "agencyID", RepresentationInfo.AgencyId);
                        Xmlhelper.WriteAttributeIfExist(xw, "id", RepresentationInfo.Id);
                        if (RepresentationInfo.Version != "1.0")
                            Xmlhelper.WriteAttributeIfExist(xw, "version", RepresentationInfo.Version);
                        xw.WriteEndElement();
                        xw.WriteEndElement();
                    }

                    xw.WriteEndElement();
                }

                // Child MetadataAttribute list
                foreach (MetadataAttributeType meta in _metadataAttributeList)
                    meta.WriteXml(xw, prefix, "MetadataAttribute");
            }

            xw.WriteEndElement();
        }
        #endregion // XML serialize

        public override string ToString()
        {
            return ConceptInfo.Id;
        }
    }

    public class MetadataAttributeTypeList : List<MetadataAttributeType>
    {

        public bool DeleteRefcode(MetadataAttributeType crtTarget)
        {
            bool deleted = false;
            foreach (MetadataAttributeType crt in this)
            {
                if (crt == crtTarget)
                {
                    this.Remove(crt);
                    deleted = true;
                    break;
                }
                if (crt.MetadataAttributeList.DeleteRefcode(crtTarget) == true)
                {
                    deleted = true;
                    break;
                }
            }

            return deleted;
        }

        public bool IsCodeRefered(string code)
        {
            bool refered = false;
            foreach (MetadataAttributeType crt in this)
            {
                if (crt.ConceptInfo.Id == code)
                {
                    refered = true;
                    break;
                }
                if (crt.MetadataAttributeList.IsCodeRefered(code) == true)
                {
                    refered = true;
                    break;
                }
            }

            return refered;
        }

        public MetadataAttributeType GetAttributeReferingConcept(string conceptId)
        {
            MetadataAttributeType refering = null;
            MetadataAttributeType cu = null;
            foreach (MetadataAttributeType crt in this)
            {
                if (crt.ConceptInfo.Id == conceptId)
                {
                    refering = crt;
                    break;
                }
                if ((cu = crt.MetadataAttributeList.GetAttributeReferingConcept(conceptId)) != null)
                {
                    refering = cu;
                    break;
                }
            }

            return refering;
        }

        public MetadataAttributeTypeList GetCodeRefListOfCode(string code)
        {
            MetadataAttributeTypeList refered = null;
            MetadataAttributeTypeList refc = null;
            foreach (MetadataAttributeType crt in this)
            {
                if (crt.ConceptInfo.Id == code)
                {
                    refered = this;
                    break;
                }
                if ((refc = crt.MetadataAttributeList.GetCodeRefListOfCode(code)) != null)
                {
                    refered = refc;
                    break;
                }
            }

            return refered;
        }

        public void CreateReferedAttributesTreeHierarchy(TreeNode parentNode, ReportedAttributeTypeList reportedList)
        {
            ReportedAttributeType rat = null;
            foreach (MetadataAttributeType crt in this)
            {
                string conceptId = crt.ConceptInfo.Id;
                rat = reportedList.GetReportedAttributeOfConcept(conceptId);
                if (rat == null)
                {
                    rat = new ReportedAttributeType();
                    rat.ConceptID = conceptId;
                }

                TreeNode nd = new TreeNode(conceptId);
                nd.Tag = rat;
                parentNode.Nodes.Add(nd);
                crt.MetadataAttributeList.CreateReferedAttributesTreeHierarchy(nd, reportedList);
            }
        }

        public void GetTreeHierarchyAsNodes(TreeNode parentNode)
        {
            foreach (MetadataAttributeType crt in this)
            {
                string name = crt.ConceptInfo.Id;
                TreeNode nd = new TreeNode(name);
                nd.Tag = crt;
                parentNode.Nodes.Add(nd);
                crt.MetadataAttributeList.GetTreeHierarchyAsNodes(nd);
            }
        }

        // Create a MetaDataset record of reported attributes hierarchy
        public void CreateMetadatasetReportedAttributes(ReportedAttributeTypeList ral)
        {
            foreach (MetadataAttributeType crt in this)
            {
                ReportedAttributeType r = new ReportedAttributeType();
                r.ConceptID = crt.ConceptInfo.Id;
                ral.Add(r);
                crt.MetadataAttributeList.CreateMetadatasetReportedAttributes(r.ReportedAttributeList);
            }
        }

    }


}
